#pragma once
#include <GL/freeglut.h>
#include <glutwindow.h>
#include <glm/glm.hpp> 
#include <glm/gtc/matrix_transform.hpp> 

#include <vector>
#include <string>
#include <map>
#include <sstream>
using namespace std;
using namespace glm;
class Punto;
class Punto{
	public:
		float x,y,z,u,v;
		vec3 normal;
		
		Punto(){
			x=y=z=u=v=0.0;
			normal = vec3(0.0,0.0,0.0);
		}
		Punto(float _x, float _y, float _z){
			x=_x;
			y=_y;
			z=_z;
		}

		void  Punto::setNormal(vec3 norm){
			normal = norm;
		}

		void  Punto::setUV(float _u, float _v){
			u=_u;
			v=_v;
		}

		vec3 getNormal(){
			return normal;
		}
	/*	string Punto::toString(){
			string s("");
			s+= ("x: " + x + ", y: " + y +", z: "+ z);
			return s;
		}*/

		 bool Punto::operator==(const Punto &otro){
			 if (otro.x==this->x && otro.y==this->y && otro.z==this->z)
				 return true;
			 return false;
		 }

		 bool Punto::operator!=(const Punto &otro){
			 if (otro.x==this->x && otro.y==this->y && otro.z==this->z)
				 return false;
			 return true;
		 }

		 Punto Punto::operator=(const Punto &otro){
			this->x =otro.x;
			this->y =otro.y;
			this->z =otro.z;
			return *this;
		}

		 Punto Punto::operator-(const Punto &otro){
			this->x -=otro.x;
			this->y -=otro.y;
			this->z -=otro.z;
			return *this;
		 }
}; 

class myWindow : public cwc::glutWindow
{
public:
    myWindow();

    virtual void OnRender(void);
    virtual void OnIdle();

    // When OnInit is called, a render context (in this case GLUT-Window) 
    // is already available!
    virtual void OnInit();

    virtual void OnResize(int w, int h);
    virtual void OnClose(void);
    virtual void OnMouseDown(int button, int x, int y);
    virtual void OnMouseUp(int button, int x, int y);
    virtual void OnMouseWheel(int nWheelNumber, int nDirection, int x, int y);
	virtual void OnMouseMove(int x, int y);
    virtual void OnKeyDown(int nKey, char cAscii);
	virtual void OnLeftMouseDrag(int x, int y);
    virtual void OnKeyUp(int nKey, char cAscii);

    // Scene functions
    void myWindow::renderArm(glm::mat4 model_matrix);
	void myWindow::renderCrane(glm::mat4 model_matrix, int nColBlocks, int nArmBlocks, int nCounterWeightBlocks);
	void myWindow::renderBuilding(glm::mat4 model_matrix, int nFloors, bool firstPass);

private:
	bool cambioEscena;
    void changeObjectColor(float r, float g, float b);

    void createSpiralSphere(const float radius, const unsigned int loops, const unsigned int segmentsPerLoop);
    void createGrid(int size);
	void createCloth(int size);
	void createTriangleCloth();
	void createTriangleCloth2();
    void createCube();
	void myWindow::createSurface();

    void renderSpiralSphere(glm::mat4 model_matrix);
    void renderGrid(glm::mat4 model_matrix);
    void renderCloth(glm::mat4 model_matrix);
    void renderTriangleCloth(glm::mat4 model_matrix);
    void renderTriangleCloth2(glm::mat4 model_matrix);
    void renderFullCloth(glm::mat4 model_matrix, int size);
    void renderCube(glm::mat4 model_matrix, char* texture);
	void myWindow::renderCube(glm::mat4 model_matrix,GLuint programHandler, int textureRepetition);
    void myWindow::renderCubeViejo(glm::mat4 model_matrix);
    float degreesToRadians(float degrees);

    glm::mat4 view_matrix;

    // Cylinder:
    void createCylinder();
    GLfloat* cylinderPositionData;
    unsigned int cylinderPositionDataSize;
    GLfloat* cylinderNormalData;
	unsigned int cylinderNormalDataSize;
    GLfloat* cylinderTextureData;
    unsigned int cylinderTextureDataSize;
    void renderCylinder(glm::mat4 model_matrix);

    // Sphere
    GLfloat* sphere_vertex_buffer;
    GLuint* sphere_index_buffer;
    std::vector<float> vertex_buffer;
    std::vector<unsigned int> index_buffer;
    
    // Cube
    GLfloat* cube_vertex_buffer;
    GLfloat* cube_normal_buffer;
    GLuint* cube_index_buffer;
    unsigned int cube_vertex_buffer_size;
    unsigned int cube_normal_buffer_size;
    unsigned int cube_index_buffer_size;
    
    // Grid 
    GLfloat* grid_vertex_buffer;
    unsigned int grid_vertex_buffer_size;
    GLuint* grid_index_buffer;
    unsigned int grid_index_buffer_size;
	
	// Surface 
	vector<vector<Punto> > puntosSurface;

	// Cloth with triangles
	GLfloat* cloth_triangle_vertex_buffer;
	unsigned int cloth_triangle_vertex_buffer_size;
	GLuint* cloth_triangle_index_buffer;
	unsigned int cloth_triangle_index_buffer_size;
	GLfloat* cloth_triangle_normal_buffer;
	unsigned int cloth_triangle_normal_buffer_size;
	
    // Cloth 
    GLfloat* cloth_vertex_buffer;
    unsigned int cloth_vertex_buffer_size;
    GLuint* cloth_index_buffer;
    unsigned int cloth_index_buffer_size;
	GLfloat* cloth_normal_buffer;
	unsigned int cloth_normal_buffer_size;
	
    GLuint programHandle;
    GLuint vertShader;
	GLuint fragShader;
    
	GLuint clothProgramHandle;
	GLuint clothVertShader;
    
	GLuint textureProgramHandle;
	GLuint textureVertShader;
	GLuint textureFragShader;

	GLuint shadowProgramHandle;
	GLuint shadowVertShader;
	GLuint shadowFragShader;

	GLuint specularProgramHandle;
	GLuint specularVertShader;
	GLuint specularFragShader;


	GLuint textureNONSHADOWProgramHandle;
	GLuint textureNONSHADOWVertShader;
	GLuint textureNONSHADOWFragShader;

	GLuint normalMappingNONSHADOWProgramHandle;
	GLuint normalMappingNONSHADOWVertShader;
	GLuint normalMappingNONSHADOWFragShader;

	GLuint specularNONSHADOWProgramHandle;
	GLuint specularNONSHADOWVertShader;
	GLuint specularNONSHADOWFragShader;

	GLuint normalMappingProgramHandle;
	GLuint normalMappingVertShader;
	GLuint normalMappingFragShader;

	//Camera

	float cameraEyeX, cameraEyeY, cameraEyeZ;
	float cameraEyeXSpeed, cameraEyeYSpeed, cameraEyeZSpeed, cameraEyeSpeed;
	float orbitRadius, orbitCenterX, orbitCenterY, orbitCenterZ;
	float orbitHeight;

	int mousePreviousX,mousePreviousY;
	float zScrollSpeed, yScrollSpeed, xScrollSpeed;
	float lookAtY, lookAtZ, lookAtX;
	float scrollMargin;
	float cameraTheeta, cameraPhi, cameraTheetaSpeed, cameraPhiSpeed;

	void myWindow::setUpCamera();
	//Crane
	float craneAngle;
	float craneAngleSpeed;
	void addCraneAngle(float n);
	void myWindow::renderCraneSection(glm::mat4 model_matrix);
	void myWindow::renderCraneBlock(glm::mat4 model_matrix);
	void myWindow::renderCraneColumn(glm::mat4 model_matrix,int numberOfBlocks);
	void myWindow::renderCraneCabin(glm::mat4 model_matrix,float height);
	void myWindow::renderCraneArm(glm::mat4 model_matrix,int numberOfBlocks, float height);
	void myWindow::renderCounterWeight(glm::mat4 model_matrix,int numberOfBlocks, float height);
	void myWindow::resetCamera();
	void myWindow::renderAltSlab(glm::mat4 model_matrix,bool firstPass);
	void myWindow::renderPane(glm::mat4 model_matrix, bool transparent);
	void myWindow::renderWall(glm::mat4 model_matrix,bool firstPass);

	//Building
	void myWindow::renderColumn(glm::mat4 model_matrix,bool firstPass);
	void renderSlab(glm::mat4 model_matrix);
	void renderStairs(glm::mat4 model_matrix);
	void renderFloor(glm::mat4 model_matrix, bool pair,bool firstPass);

	//Texture
	void  myWindow::loadTexture(const char* filename);
	void  myWindow::loadTextures();
	void myWindow::initTexture(const char* filename);
	void myWindow::initNormalMap(const char* filename);
	GLuint textureid;
	int lastAdded;
	map<string,GLuint> textures;

	//Surfaces
	void myWindow::renderPilaDeArena(mat4 model_matrix, char* textura);
	void myWindow::renderMonticulo(mat4 model_matrix, char* textura);
	void myWindow::renderSurfaces(glm::mat4 model_matrix);
	void myWindow::createSurfaces();
	void myWindow::createCurves();
	void myWindow::createSplines();
	void myWindow::createSweepSurface();
	void myWindow::renderSweepSurface(glm::mat4 model_matrix);
	void myWindow::renderPipe(glm::mat4 model_matrix);
	void myWindow::renderCurves(glm::mat4 model_matrix);
	void myWindow::renderAltCylinder(glm::mat4 model_matrix,char* texture);
	void myWindow::renderAltCylinder(glm::mat4 model_matrix,GLuint programHandler,int textureRepetition);
	map < string, vector <vector<Punto>> > superficies;
	map < string, vector <Punto> > curvas;

	//Surface of revolution (SOR is for Surface of Revolution)
	void myWindow::createSurfaceOfRevolution(vector<Punto> controlPoints, string name);
	void myWindow::renderSurfaceOfRevolution(glm::mat4 modelMatrix, string name);
	map<string,vector<glm::vec3> > sor_vertex_buffer;
	map<string,vector<glm::vec3> > sor_normal_buffer;

	// Trees
	int nTrees;
	void myWindow::createTreeCup();
	void myWindow::renderTree(glm::mat4 modelMatrix,string name);

	// Ground
	void myWindow::renderGround(glm::mat4 model_matrix);

	//Skybox
	void myWindow::renderSkyBox(glm::mat4 model_matrix);

	//Shadows

	GLuint frameBuffer, depthTexture;
	void myWindow::updateShadowShaderParams(vec3 lightPos);
	void myWindow::computeDepthMVP(vec3 lightInvDir,mat4 depthModelMatrix, vec3 eyePos);
	void myWindow::initShadows();

	void myWindow::renderShadowsToTexture(vec3 lightDir, mat4 model_matrix);
	void myWindow::renderObjectsForShadowing(mat4 model_matrix,bool firstPass);
	void myWindow::renderShadowedObjects(vec3 lightPos, mat4 proj_mat);

	mat4 depthMVP;
	void myWindow::test(mat4 m);
	void myWindow::initDepthTexture();
	mat4 biasMatrix;
	mat4 depthProjectionMatrix;
	
	void myWindow::renderWithShadows(float lightX, float lightY,float lightZ, mat4 model_matrix_grid, mat4 projection_matrix);
	void myWindow::renderWithoutShadows(float lightX, float lightY,float lightZ, mat4 model_matrix_grid, mat4 projection_matrix);


	//Shaders
	void compileStandardShaders();
	void compileTextureShaders();
	void compileTextureNONSHADOWShaders();
	void compileNormalMappingNONSHADOWShaders();
	void compileNormalMappingShaders();
	void compileShadowShaders();
	void compileSpecularShaders();
	void complieSpecularNONSHADOWShaders();
	
	void linkStandardShaders();
	void linkTextureShaders();
	void linkTextureNONSHADOWShaders();
	void linkNormalMappingNONSHADOWShaders();
	void linkNormalMappingShaders();
	void linkShadowShaders();
	void linkSpecularShaders();
	void linkSpecularNONSHADOWShaders();

	//Texture

	void initTextureShaderParams(vec3 lightPos, mat4 projection_matrix, mat4 model_matrix);
	void myWindow::initTextureNONSHADOWShaderParams(vec3 lightPos, mat4 projection_matrix, mat4 model_matrix);
	void myWindow::initSpecularNONSHADOWShaderParams(vec3 lightPos, mat4 projection_matrix, mat4 model_matrix);
	void myWindow::initNormalMapNONSHADOWShaderParams(vec3 lightPos, mat4 projection_matrix, mat4 model_matrix);
	void myWindow::initNormalMapShaderParams(vec3 lightPos, mat4 projection_matrix, mat4 model_matrix);
	void renderTexturedObjects(mat4 model_matrix,bool firstPass);
    
};